<!--
 * Tencent is pleased to support the open source community by making 蓝鲸 available.
 * Copyright (C) 2017 Tencent. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
-->

<template>
  <div class="cmdb-tag-input"
    :style="{
      height: fixedHeight ? inputHeight + 'px' : 'auto'
    }"
    @mousedown="() => (shouldUpdate = false)"
    @click="focus">
    <div class="tag-input-layout">
      <div class="tag-input-container"
        ref="container"
        :class="{
          focus: isFocus,
          disabled: disabled,
          placeholder: !localValue.length && !isFocus,
          'is-fast-clear': fastClear,
          'is-loading': loading,
          'is-flex-height': !fixedHeight
        }"
        :style="containerStyle"
        :data-placeholder="placeholder"
        @mousewheel="handleContainerScroll">
        <template v-if="multiple || !isFocus">
          <span class="tag-input-selected"
            v-for="(tag, index) in localValueData"
            ref="selected"
            :key="tag.value"
            @click.stop
            @mousedown.left.stop="handleSelectedMousedown($event, index)"
            @mouseup.left.stop="handleSelectedMouseup($event, index)"
            @mouseenter="handleSelectedMouseenter($event, tag)"
            @mouseleave="handleSelectedMouseleave($event, tag)">
            <template v-if="renderTag">
              <render-tag :tag="tag" :index="index"></render-tag>
            </template>
            <template v-else>
              <span class="tag-input-selected-value">
                {{getDisplayText(tag)}}
              </span>
            </template>
            <i class="tag-input-selected-clear bk-icon icon-close" v-if="tagClearable"
              @mouseup.left.stop
              @mousedown.left.stop="handleRemoveMouseDown"
              @click.stop.prevent="handleRemoveSelected(tag, index)">
            </i>
          </span>
        </template>
        <span ref="input" class="tag-input-input"
          spellcheck="false"
          contenteditable
          v-show="isFocus"
          @click.stop
          @input="handleInput($event)"
          @blur="handleBlur"
          @paste.prevent.stop="handlePaste($event)"
          @keydown="handleKeydown($event)">
        </span>
      </div>
      <i class="tag-input-clear bk-icon icon-close-circle-shape"
        v-if="fastClear && !disabled && localValue.length"
        @click.stop="handleFastClear">
      </i>
    </div>
  </div>
</template>

<script>
  import throttle from 'lodash.throttle'
  import RenderTag from './render-tag.js'
  import AlternateList from './alternate-list'
  import Vue from 'vue'
  import Tippy from 'bk-magic-vue/lib/utils/tippy'
  import Axios from 'axios'
  import has from 'has'
  import i18n from '@/i18n'
  export default {
    name: 'cmdb-tag-input',
    components: {
      RenderTag
    },
    props: {
      value: {
        type: Array,
        default: () => ([])
      },
      placeholder: {
        type: String,
        default: i18n.t('请输入')
      },
      disabled: {
        type: Boolean,
        default: false
      },
      multiple: {
        type: Boolean,
        default: true
      },
      allowCreate: {
        type: Boolean,
        default: true
      },
      createOnly: Boolean,
      focusRowLimit: {
        type: Number,
        default: 6
      },
      defaultAlternate: {
        type: [String, Array, Function],
        validator(value) {
          return value === 'history' || typeof value === 'function' || value instanceof Array
        }
      },
      searchFromDefaultAlternate: {
        type: Boolean,
        default: true
      },
      historyKey: String,
      historyLabel: {
        type: String,
        default: i18n.t('最近选择')
      },
      historyRecord: {
        type: Number,
        default: 5
      },
      fuzzySearchMethod: Function,
      exactSearchMethod: Function,
      emptyText: {
        type: String,
        default: i18n.t('暂无数据')
      },
      tagClearable: {
        type: Boolean,
        default: true
      },
      fastClear: Boolean,
      renderList: Function,
      renderTag: Function,
      displayTagTips: Boolean,
      tagTipsContent: Function,
      tagTipsDelay: {
        type: Number,
        default: 300
      },
      fixedHeight: {
        type: Boolean,
        default: true
      },
      disabledData: {
        type: Array,
        default: () => ([])
      },
      listScrollHeight: [Number, String],
      searchLimit: {
        type: Number,
        default: 20
      },
      pasteFormatter: {
        type: Function,
        default(value) {
          return value.trim()
        }
      },
      pasteValidator: {
        type: Function,
        default: values => values
      },
      panelWidth: {
        type: [Number, String],
        validator(value) {
          const pixel = parseInt(value, 10)
          return pixel >= 190
        }
      }
    },
    data() {
      return {
        inputHeight: 32,
        singleRowHeight: 30,
        inputValue: '',
        inputIndex: 0,
        highlightIndex: -1,
        shouldUpdate: true,
        isFocus: false,
        overflowTagIndex: null,
        currentData: [],
        matchedData: [],
        flattenedData: [],
        scheduleSearch: throttle(this.search, 800, { leading: false }),
        popoverInstance: null,
        alternateContent: null,
        selectedTipsTimer: {},
        overflowTagNode: null,
        loading: false
      }
    },
    computed: {
      containerStyle() {
        const style = {}
        if (this.isFocus) {
          style.maxHeight = this.fixedHeight ? `${this.focusRowLimit * this.singleRowHeight}px` : 'auto'
        } else if (this.fixedHeight) {
          style.height = `${this.singleRowHeight}px`
        }
        return style
      },
      localValue: {
        get() {
          return [...this.value]
        },
        set(value) {
          this.$emit('input', value)
          this.$emit('change', value)
        }
      },
      localValueData() {
        return this.localValue.map((value) => {
          const tag = this.currentData.find(tag => tag.value === value)
          return tag || { value }
        })
      }
    },
    watch: {
      inputValue(value) {
        if (value.length) {
          this.highlightIndex = -1
          this.updateScroller()
          this.scheduleSearch(value)
        } else if (this.isFocus) {
          this.scheduleSearch()
        }
      },
      isFocus(isFocus) {
        if (isFocus) {
          this.search()
          this.$emit('focus')
        } else {
          this.reset()
          this.$emit('blur')
        }
        this.calcOverflow()
      },
      localValue() {
        this.calcOverflow()
        this.getCurrentData()
      },
      highlightIndex() {
        this.updateScroller()
      }
    },
    created() {
      this.getCurrentData()
    },
    mounted() {
      this.calcOverflow()
    },
    methods: {
      async getCurrentData() {
        try {
          if (this.exactSearchMethod) {
            this.currentData = await this.exactSearchMethod(this.localValue)
          } else {
            console.warn('No exact search method has been set')
          }
        } catch (error) {
          console.error(error)
        }
      },
      // 搜索，滚动加载时next可设置为页码，无更多设置为false
      async search(value, next) {
        if (this.createOnly) return
        try {
          const popoverInstance = this.getPopoverInstance()
          const alternateContent = this.getAlternateContent()
          popoverInstance.setContent(alternateContent.$el)
          this.showPopover()
          alternateContent.loading = !!value
          const { results: data, next: nextPage } = await new Promise(async (resolve, reject) => {
            if (value) {
              try {
                const promise = [(this.fuzzySearchMethod || this.defaultFuzzySearchMethod)(value, next)]
                if (this.searchFromDefaultAlternate) {
                  promise.push(this.getDefaultAlternateData(value))
                }
                const [fuzzySearchData, defaultAlternateData] = await Promise.all(promise)
                if (defaultAlternateData) {
                  fuzzySearchData.results.unshift(...defaultAlternateData.results)
                }
                resolve(fuzzySearchData)
              } catch (error) {
                reject(error)
              }
            } else {
              const defaultAlternateData = this.getDefaultAlternateData()
              resolve(defaultAlternateData)
            }
          })
          if (!this.isFocus) {
            return
          }
          const { matched, flattened } = this.filterData(data)
          if (!value && !flattened.length) {
            this.hidePopover()
            return
          }
          this.matchedData = next ? [...this.matchedData, ...matched] : matched
          this.flattenedData = next ? [...this.flattenedData, ...flattened] : flattened
          this.highlightIndex = flattened.length && !!this.inputValue ? 0 : -1
          alternateContent.next = nextPage
          alternateContent.keyword = value
          alternateContent.matchedData = this.matchedData
          alternateContent.loading = false
        } catch (e) {
          console.error(e)
          if (e.message === 'expire' || Axios.isCancel(e)) {
            return
          }
          this.matchedData = []
          this.flattenedData = []
        }
      },
      async getDefaultAlternateData(keyword) {
        let data = []
        const isMatch = (tag, keyword) => tag.value.toLowerCase().indexOf(keyword.toString().toLowerCase()) > -1
        if (this.defaultAlternate === 'history') {
          data = [{ display_name: this.historyLabel, children: this.getHistoryData() }]
        } else if (this.defaultAlternate instanceof Array) {
          data = this.defaultAlternate
        } else if (typeof this.defaultAlternate === 'function') {
          data = await this.defaultAlternate()
        }
        if (keyword) {
          const filterResult = []
          data.forEach((tag) => {
            if (has(tag, 'children')) {
              const children = tag.children.filter(child => isMatch(child, keyword))
              if (children.length) {
                filterResult.push({
                  ...tag,
                  children
                })
              }
            } else if (isMatch(tag, keyword)) {
              filterResult.push(tag)
            }
          })
          data = filterResult
        }
        return Promise.resolve({ results: data, next: false })
      },
      // 过滤已被选择的
      filterData(data) {
        const matched = []
        const flattened = []
        data.forEach((tag) => {
          if (has(tag, 'children')) {
            // eslint-disable-next-line max-len
            const children = tag.children.filter(child => !flattened.some(flattenedTag => flattenedTag.value === child.value))
            if (this.multiple) {
              const unexistTag = children.filter(child => !this.localValue.includes(child.value))
              if (unexistTag.length) {
                tag.children = unexistTag
                matched.push(tag)
                flattened.push(...unexistTag)
              }
            } else {
              matched.push(tag)
              flattened.push(...children)
            }
            return
          }
          const exist = this.localValue.includes(tag.value)
          const repeat = flattened.some(flattenedTag => flattenedTag.value === tag.value)
          if ((!this.multiple || !exist) && !repeat) {
            matched.push(tag)
            flattened.push(tag)
          }
        })
        return {
          matched,
          flattened
        }
      },
      // 默认模糊搜索方法，可通过fuzzySearchMethod自定义
      async defaultFuzzySearchMethod(value, next) {
        if (!this.fuzzySearchMethod) {
          console.warn('No fuzzy search method has been set')
          return Promise.resolve({ next: false, results: [] })
        }
        return this.fuzzySearchMethod(value, next)
      },
      // 已选择的标签tootips内容，可通过tagTipsContent自定义
      async getTagTips(instance, value) {
        try {
          const contentElement = document.createElement('span')
          if (typeof this.tagTipsContent === 'function') {
            const content = await this.tagTipsContent(value)
            contentElement.innerHTML = content
          } else {
            const tag = await (this.exactSearchMethod || this.defaultExactSearchMethod)(value)
            contentElement.innerHTML = tag ? tag.text : 'Non existing tag'
          }
          instance.setContent(contentElement)
        } catch (e) {
          console.error(e)
          instance.setContent(e.message)
        }
      },
      // 默认精确搜索方法，通过此方法获取单个信息
      defaultExactSearchMethod(value) {
        if (!this.exactSearchMethod) {
          console.warn('No exact search method has been set')
          return Promise.resolve({})
        }
        return this.exactSearchMethod(value)
      },
      // 创建/获取备选面板popover实例
      getPopoverInstance() {
        if (!this.popoverInstance) {
          // eslint-disable-next-line new-cap
          this.popoverInstance = Tippy(this.$refs.input, {
            theme: 'light tag-input-popover',
            appendTo: document.body,
            trigger: 'manual',
            placement: 'bottom-start',
            // distance: 5,
            arrow: false,
            hideOnClick: false,
            content: '',
            interactive: true,
            boundary: 'window',
            onHide: () => {
              this.handlePopoverHide()
            },
            onShow: () => this.isFocus
          })
        }
        return this.popoverInstance
      },
      // 创建/获取备选面板内容
      getAlternateContent() {
        if (!this.alternateContent) {
          this.alternateContent = new Vue(AlternateList)
          this.alternateContent.tagInput = this
          this.alternateContent.$mount()
        }
        return this.alternateContent
      },
      // 获取最近输入
      getHistoryData() {
        try {
          if (this.historyKey) {
            const data = JSON.parse(window.localStorage.getItem(this.historyKey)) || []
            return data.filter(tag => !this.disabledData.includes(tag.value))
          }
          throw new Error('History key not provide')
        } catch (e) {
          console.error(e)
          return []
        }
      },
      // 更新最近输入
      updateHistoryData(tag) {
        if (this.historyKey) {
          try {
            const histories = this.getHistoryData()
            const exist = histories.findIndex(history => history.value === tag.value)
            if (exist > -1) {
              histories.splice(exist, 1)
            }
            Array.isArray(tag) ? histories.unshift(...tag) : histories.unshift(tag)
            // eslint-disable-next-line max-len
            const newHistories = histories.filter(history => !this.disabledData.includes(history.value)).slice(0, this.historyRecord)
            window.localStorage.setItem(this.historyKey, JSON.stringify(newHistories))
          } catch (e) {
            console.error(e)
          }
        }
      },
      // 更新面板定位
      updatePopover() {
        this.popoverInstance && this.popoverInstance.popperInstance && this.popoverInstance.popperInstance.update()
      },
      showPopover() {
        this.updatePopover()
        this.popoverInstance && this.popoverInstance.show(0)
      },
      hidePopover() {
        this.popoverInstance && this.popoverInstance.hide(0)
      },
      // 面板隐藏后重置匹配上的数据
      handlePopoverHide() {
        this.$nextTick(() => {
          this.matchedData = []
          this.flattenedData = []
          this.alternateContent.matchedData = []
        })
      },
      // 显示的文本
      getDisplayText(tag) {
        const isObject = typeof tag === 'object'
        return isObject ? tag.text || tag.value : tag
      },
      // 元素整体点击, 获得焦点，初始化输入相关内容
      focus() {
        if (this.disabled) {
          return false
        }
        this.clearOverflowTimer()
        this.inputIndex = this.localValue.length
        if (!this.multiple && this.value.length) {
          this.inputValue = this.getDisplayText(this.value[0])
          this.$refs.input.innerHTML = this.inputValue
          this.moveInput(0, { selectRange: true })
        } else {
          this.moveInput(0)
        }
      },
      // 选择面板显示时，禁止已选容器的滚动，防止面板滚动后错位
      handleContainerScroll(event) {
        this.popoverInstance && this.popoverInstance.state.isVisible && event.preventDefault()
      },
      // 已选择click事件拆分为mousedown,mouseup, 因为click事件晚于input的blur事件，但mousedown早于blur
      // shouldUpdate = false, 阻止input blur后续处理
      handleSelectedMousedown() {
        if (this.disabled) {
          return false
        }
        this.shouldUpdate = false
      },
      // 根据点击的位置判断在点击的前方还是后方进行输入
      handleSelectedMouseup(event, index) {
        if (this.disabled) {
          return false
        }
        if (this.multiple) {
          const $referenceTarget = event.target
          const { offsetWidth } = $referenceTarget
          const eventX = event.offsetX
          // const $input = this.$refs.input
          this.inputIndex = eventX > (offsetWidth / 2) ? index + 1 : index
          this.moveInput(0)
        } else {
          this.inputValue = this.getDisplayText(this.value[0])
          this.$refs.input.innerHTML = this.inputValue
          this.moveInput(0, { selectRange: true })
        }
      },
      // 已选的鼠标划过事件，如果配置了显示tips，则在一定延时后显示
      handleSelectedMouseenter(event, { value }) {
        if (!this.displayTagTips) return
        const target = event.currentTarget
        // eslint-disable-next-line no-underscore-dangle
        if (target._tag_tips_) {
          return false
        }
        this.selectedTipsTimer[value] = setTimeout(() => {
          // eslint-disable-next-line no-underscore-dangle,new-cap
          target._tag_tips_ = Tippy(target, {
            theme: 'light small-arrow tag-selected-tips',
            boundary: 'window',
            appendTo: document.body,
            arrow: true,
            content: 'loading...',
            placement: 'top',
            interactive: true,
            onShow: instance => this.getTagTips(instance, value)
          })
          // eslint-disable-next-line no-underscore-dangle
          target._user_tips_.show()
          delete this.selectedTipsTimer[value]
        }, this.tagTipsDelay)
      },
      // 在tips延时执行前离开，放弃执行
      handleSelectedMouseleave(event, { value }) {
        if (this.displayTagTips) {
          this.selectedTipsTimer[value] && clearTimeout(this.selectedTipsTimer[value])
        }
      },
      // 删除单个mousedown,阻止input blur
      handleRemoveMouseDown() {
        this.shouldUpdate = false
      },
      // 删除单个
      handleRemoveSelected({ value }, index) {
        if (this.disabled) {
          return false
        }
        const localValue = [...this.localValue]
        localValue.splice(index, 1)
        this.localValue = localValue
        this.reset()
        if (this.isFocus) {
          this.moveInput(index >= this.inputIndex ? 0 : -1)
        } else {
          this.handleBlur()
        }
        this.$emit('remove-selected', value)
      },
      // 选择列表mousedown, 阻止input blur
      handleTagMousedown() {
        this.shouldUpdate = false
      },
      // 选择列表，区分单选多选
      handleTagMouseup(tag, disabled) {
        if (disabled || this.disabled) {
          this.moveInput(0)
          return false
        }
        this.updateHistoryData(tag)
        this.currentData.push(tag)
        if (this.multiple) {
          const localValue = [...this.localValue]
          localValue.splice(this.inputIndex, 0, tag.value)
          this.localValue = localValue
          setTimeout(() => {
            this.moveInput(1)
            this.setSelection({ reset: true })
            this.search()
          }, 0)
        } else {
          this.localValue = [tag.value]
          this.reset()
          this.handleBlur()
        }
        this.$emit('select-tag', tag)
      },
      // 选择列表分组 mousedown, 阻止input blur
      handleGroupMousedown() {
        this.shouldUpdate = false
      },
      // 点击选择列表分组会导致input 失去焦点，mouseup后重新获得焦点
      handleGroupMouseup() {
        this.moveInput(0)
      },
      // input 通过contenteditable模拟，已输入情况下，再次点击同一位置，光标会在文字最前方
      // 此方法多选时，将光标移至文字末尾，单选时，选中文本
      setSelection(option = {}) {
        if (option.reset) {
          this.reset()
        }
        this.isFocus = true
        this.shouldUpdate = true
        this.$nextTick(() => {
          const $input = this.$refs.input
          $input.focus()
          const range = window.getSelection()
          range.selectAllChildren($input)
          !option.selectRange && range.collapseToEnd()
        })
      },
      // 集中分配input 的键盘事件
      handleKeydown(event) {
        if (this.loading) {
          event.preventDefault()
          event.stopPropagation()
          return
        }
        const { key } = event
        const keyMap = {
          Enter: this.handleEnter,
          Backspace: this.handleBackspace,
          Delete: this.handleBackspace,
          ArrowLeft: this.handleArrow,
          ArrowRight: this.handleArrow,
          ArrowUp: this.handleArrow,
          ArrowDown: this.handleArrow
        }
        if (has(keyMap, key)) {
          keyMap[key](event)
        }
        this.$emit('keydown', event)
      },
      // 通过键盘enter进行确认
      // 1.如果有备选高亮，则直接选中该项
      // 2.如果配置允许创建，允许输入不在备选列表中的，则进行相关值的设定
      handleEnter(event) {
        event.preventDefault()
        this.shouldUpdate = false
        if (this.highlightIndex !== -1) {
          const { value } = this.flattenedData[this.highlightIndex]
          const disabled = this.disabledData.includes(value)
          if (disabled) {
            return false
          }
          if (this.multiple) {
            const localValue = [...this.localValue]
            localValue.splice(this.inputIndex, 0, value)
            this.localValue = localValue
            this.moveInput(1, { reset: true })
          } else {
            this.localValue = [value]
            this.reset()
            this.handleBlur()
          }
        } else if (this.inputValue) {
          const exist = this.localValue.includes(this.inputValue)
          if (exist || !this.allowCreate) {
            this.reset()
          } else if (this.multiple) {
            const localValue = [...this.localValue]
            localValue.splice(this.inputIndex, 0, this.inputValue)
            this.localValue = localValue
            this.moveInput(1, { reset: true })
          } else {
            this.localValue = [this.inputValue]
            this.reset()
            this.handleBlur()
          }
        } else {
          this.reset()
          this.handleBlur()
        }
        this.hidePopover()
      },
      // 删除事件，无文本输入时删除前一个
      handleBackspace() {
        if (this.inputValue || !this.localValue.length || !this.inputIndex) {
          return true
        }
        this.shouldUpdate = false
        const localValue = [...this.localValue]
        localValue.splice(this.inputIndex - 1, 1)
        this.localValue = localValue
        this.moveInput(-1)
        this.search()
      },
      // 箭头事件
      // 1.左右事件处理为无文本时移动光标在已选中的位置
      // 2.上下事件处理备选高亮状态及备选列表的滚动位置
      handleArrow(event) {
        const arrow = event.key
        if (['ArrowLeft', 'ArrowRight'].includes(arrow)) {
          if (this.inputValue || !this.localValue.length) {
            return true
          }
          if (arrow === 'ArrowLeft' && this.inputIndex !== 0) {
            this.moveInput(-1)
          } else if (arrow === 'ArrowRight' && this.inputIndex !== this.localValue.length) {
            this.moveInput(1)
          }
        } else if (this.flattenedData.length) {
          event.preventDefault()
          if (arrow === 'ArrowDown') {
            if (this.highlightIndex < (this.flattenedData.length - 1)) {
              this.highlightIndex = this.highlightIndex + 1
            } else if (this.alternateContent && this.alternateContent.next) {
              const { alternateList } = this.alternateContent.$refs
              alternateList.scrollTop = alternateList.scrollTop + 32
              this.alternateContent.handleScroll()
            } else {
              this.highlightIndex = 0
            }
          } else if (arrow === 'ArrowUp' && this.highlightIndex !== -1) {
            this.highlightIndex = this.highlightIndex - 1
          }
        }
      },
      // 处理input输入
      handleInput(event) {
        if (this.loading) {
          event.preventDefault()
          event.stopPropagation()
          return
        }
        this.inputValue = this.$refs.input.textContent.trim()
      },
      // 处理input blur事件，在其他元素的mousedown事件中控制shouldUpdate，达到阻止后续逻辑的能力
      // blur时，如果允许输入不存在备选列表中的值，则以当前值填充，否则匹配备选列表
      handleBlur() {
        if (!this.shouldUpdate) {
          return true
        }
        this.isFocus = false
        this.hidePopover()
      },
      // 获取匹配当前输入文本的数据
      getMatchedTag(nameToMatch) {
        const tag = this.flattenedData.find((tag) => {
          const { value } = tag
          const { text } = tag
          const isMatch = [value, text].some(name => name.toLowerCase() === nameToMatch.toLowerCase())
          const isSelected = this.localValue.includes(value)
          return isMatch && !isSelected
        })
        return tag
      },
      // 粘贴事件，逗号或者分号分隔
      async handlePaste(event) {
        this.hidePopover()
        if (this.loading) {
          event.preventDefault()
          event.stopPropagation()
        }
        try {
          this.loading = true
          const pasteStr = event.clipboardData.getData('text').trim()
          const values = pasteStr.split(/,|;|\n/).map(value => this.pasteFormatter(value))
            .filter(value => value.length)
          const uniqueValues = [...new Set(values)]
          if (!uniqueValues.length) {
            return
          }
          const validValues = await this.pasteValidator(uniqueValues)
          const newValues = validValues.filter(value => !this.localValue.includes(value))
          if (!validValues.length) {
            return
          }
          const localValue = [...this.localValue]
          localValue.splice(this.inputIndex, 0, ...newValues)
          this.localValue = localValue
          this.isFocus && this.moveInput(newValues.length, { reset: true })
        } catch (error) {
          console.error(error)
        } finally {
          this.loading = false
        }
      },
      getSelectedDOM() {
        return Array.from(this.$refs.container.querySelectorAll('.tag-input-selected'))
      },
      // 移动光标位置
      moveInput(step, option = {}) {
        this.inputIndex = this.inputIndex + step
        this.$nextTick(() => {
          const selected = this.getSelectedDOM()
          const $referenceTarget = selected[this.inputIndex] || null
          this.$refs.container.insertBefore(this.$refs.input, $referenceTarget)
          this.setSelection(option)
          this.updatePopover()
        })
      },
      // 设置备选高亮时，当前备选项不在视图内时，进行滚动，到底/顶后循环滚动
      updateScroller() {
        if (!this.alternateContent) {
          return false
        }
        this.$nextTick(() => {
          const { highlightIndex } = this
          const $alternateList = this.alternateContent.$refs.alternateList
          if (!$alternateList) {
            return false
          }
          if (highlightIndex !== -1) {
            const $alternateItem = this.alternateContent.$refs.alternateItem[highlightIndex].$el
            const listClientHeight = $alternateList.clientHeight
            const listScrollTop = $alternateList.scrollTop
            const itemOffsetTop = $alternateItem.offsetTop
            const itemOffsetHeight = $alternateItem.offsetHeight
            const isSmaller = (itemOffsetTop + itemOffsetHeight) <= (listScrollTop + listClientHeight)
            if (itemOffsetTop >= listScrollTop && isSmaller) {
              return false
            } if (itemOffsetTop <= listScrollTop) {
              $alternateList.scrollTop = itemOffsetTop
            } else if ((itemOffsetTop + itemOffsetHeight) > (listScrollTop + listClientHeight)) {
              $alternateList.scrollTop = itemOffsetTop + itemOffsetHeight - listClientHeight
            }
          } else {
            $alternateList.scrollTop = 0
          }
        })
      },
      // 计算第二行第一个的index，在其前方插入overflow tag
      calcOverflow() {
        this.removeOverflowTagNode()
        // eslint-disable-next-line no-underscore-dangle
        if (!this._isMounted || !this.fixedHeight || this.isFocus || this.localValue.length < 2) {
          return false
        }
        this.clearOverflowTimer()
        this.overflowTimer = setTimeout(() => {
          const selectedData = this.getSelectedDOM()
          const userIndexInSecondRow = selectedData.findIndex((currentTag, index) => {
            if (!index) return false
            const previousTag = selectedData[index - 1]
            return previousTag.offsetTop !== currentTag.offsetTop
          })
          if (userIndexInSecondRow > -1) {
            this.overflowTagIndex = userIndexInSecondRow
          } else {
            this.overflowTagIndex = null
          }
          this.$refs.container.scrollTop = 0
          this.insertOverflowTag()
        }, 0)
      },
      clearOverflowTimer() {
        this.overflowTimer && clearTimeout(this.overflowTimer)
      },
      // 根据计算的overflow index，插入tag并进行校正
      insertOverflowTag() {
        if (!this.overflowTagIndex) return
        const overflowTagNode = this.getOverflowTagNode()
        const selectedTag = this.getSelectedDOM()
        const referenceTag = selectedTag[this.overflowTagIndex]
        if (referenceTag) {
          overflowTagNode.textContent = `+${this.localValue.length - this.overflowTagIndex}`
          this.$refs.container.insertBefore(overflowTagNode, referenceTag)
        } else {
          this.overflowTagIndex = null
          return
        }
        setTimeout(() => {
          const previousTag = selectedTag[this.overflowTagIndex - 1]
          if (overflowTagNode.offsetTop !== previousTag.offsetTop) {
            this.overflowTagIndex = this.overflowTagIndex - 1
            this.$refs.container.insertBefore(overflowTagNode, overflowTagNode.previousSibling)
            overflowTagNode.textContent = `+${this.localValue.length - this.overflowTagIndex}`
          }
        }, 0)
      },
      // 创建/获取溢出数字节点
      getOverflowTagNode() {
        if (this.overflowTagNode) {
          return this.overflowTagNode
        }
        const overflowTagNode = document.createElement('span')
        // eslint-disable-next-line no-underscore-dangle
        overflowTagNode.setAttribute(this.$options._scopeId, '')
        overflowTagNode.className = 'tag-input-overflow-tag'
        this.overflowTagNode = overflowTagNode
        return overflowTagNode
      },
      // 从容器中移除溢出数字节点
      removeOverflowTagNode() {
        if (this.overflowTagNode && this.overflowTagNode.parentNode === this.$refs.container) {
          this.$refs.container.removeChild(this.overflowTagNode)
        }
      },
      // 一键清空
      handleFastClear() {
        this.localValue = []
        this.$emit('clear')
      },
      // 重置选择器输入状态
      reset() {
        this.shouldUpdate = true
        this.highlightIndex = -1
        this.inputValue = ''
        this.$refs.input.innerHTML = ''
      }
    }
  }
</script>

<style lang="scss" scoped>
    @import './style.scss';
</style>

<style lang="scss">
    .tippy-tooltip.tag-input-popover-theme {
        border: 1px solid #dcdee5;
        border-radius: 2px;
        box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.1);
        color: #63656E;
        font-size: 12px;
        line-height: 24px;
        padding: 0;
    }
</style>
